前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# koa-jsonp中间件

30 秒速记

  • koa-jsonp:业务照常写 ctx.body = 对象,中间件在返回阶段检测到 callback 参数就自动包成 cb(...)
  • 没带 callback 的请求原样返回 JSON,同一接口能同时服务新老调用方
  • 洋葱模型:jsonp() 要注册在设置 ctx.body 的中间件之前,它在 await next() 之后改写响应
  • 只给明确需要的路由挂,别全局包装;回调名校验、nosniff 要自己确认有没有做
  • 这个包很久没更新,用之前看它和当前 Koa 版本是否兼容;新项目直接 CORS

koa-jsonp 把手写 JSONP 那几行封装成了中间件,业务代码只管 ctx.body = data,要不要包成回调由它根据请求自动判断。 它利用的是 Koa 的洋葱模型:先 await next() 让下游把 ctx.body 设好,回来的时候看请求里有没有 callback,有就把响应改写成 cb(JSON) 并改掉响应类型,没有就原样放行。所以它必须注册在业务中间件前面。我用这种包会注意两点:只挂在需要兼容老页面的路由上;看一下它对回调名有没有做过滤,没有就自己在前面补一层校验。

理解「koa-jsonp中间件」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

koa-jsonp 解决的问题是:已经有一批返回 JSON 的接口,现在要让老页面也能用 JSONP 调,但不想每个接口都手写一遍拼接逻辑。

它的工作方式可以用一段等价代码理解:

function jsonp({ callbackName = 'callback' } = {}) {
  const CB_RE = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/
  return async (ctx, next) => {
    await next()                                   // 先让业务把 ctx.body 设好
    const cb = ctx.query[callbackName]
    if (!cb) return                                // 没有回调参数,原样返回 JSON
    if (!CB_RE.test(cb)) {
      ctx.status = 400
      ctx.body = 'invalid callback'
      return
    }
    ctx.type = 'text/javascript'
    ctx.set('X-Content-Type-Options', 'nosniff')
    ctx.body = `/**/ ;${cb}(${JSON.stringify(ctx.body)})`
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

关键在 await next() 这一行。Koa 的中间件像洋葱一样一层层进去再一层层出来,这个中间件在「出来」的时候才去看 ctx.body,所以它必须注册在设置响应的业务中间件之前:

app.use(jsonp())        // 外层:返回时包装
app.use(router.routes()) // 内层:设置 ctx.body
@前端进阶之旅: 代码已经复制到剪贴板

请求带了 ?callback=fn 就返回 fn({...}),没带就是普通 JSON。这种「按参数切换」的设计让同一个接口能同时服务新老调用方,代价是缓存要区分 callback 参数。实际项目里建议只把它挂在需要兼容的那几条路由上,而不是全局启用,避免所有接口都多出一个可执行脚本的出口。

面试官追问

追问 1有人说装了 koa-jsonp,POST 接口也能跨域调了,对吗?
参考回答

不对。JSONP 靠 <script> 发请求,只能 GET,也带不了自定义头。中间件只负责包装响应,管不了浏览器怎么发请求,POST 跨域要配 CORS。

追问 2app.use(jsonp()) 写在路由后面,为什么不生效?
参考回答

洋葱模型里,注册在后面的中间件只有前面调了 next() 才会进去,而且它包装响应依赖 await next() 之后的阶段。放在路由后面时,路由已经设好 body 返回了,它根本没机会改写。

追问 3同一个接口既给移动端返回 JSON,又给老页面返回 JSONP,缓存会不会出问题?
参考回答

会。CDN 要是只按路径缓存,先被老页面请求,移动端就可能拿到一段 cb(...) 脚本。缓存键要带上 callback 参数,或者干脆拆成两个路径。

追问 4线上报 callback is not defined,服务端看是 200,从哪查起?
参考回答

先抓响应体,看回调名是不是请求里传的那个;再看页面在插 script 前有没有定义这个函数。中间件默认的参数名是 callback,前端要是传的是 cb=xxx,就不会被识别成 JSONP。

追问 5这个包要不要用在新项目里?
参考回答

不用。新项目没有老客户端包袱,直接 @koa/cors 配白名单。koa-jsonp 这种老包维护不活跃,引进来以后升级 Koa 大版本时还得多查一次兼容性。

# koa-jsonp中间件

koa.js 官方wiki中也介绍了不少jsonp的中间件

其中koa-jsonp是支持koa2的,使用方式也非常简单,koa-jsonp的官方demo也很容易理解

# 快速使用

30 秒速记

  • 三步:npm install koa-jsonp → app.use(jsonp()) → 业务里照常 ctx.body = data
  • 请求带 ?callback=fn 返回 fn({...}),不带就是普通 JSON
  • 先跑通仓库里的 jsonp-use-middleware 示例,再接到自己的服务里
  • 接入时补上:只挂到需要的路由、回调名校验、callback 缺失时的约定(返回 JSON 还是 400)

快速上手就是安装、注册中间件、正常写业务这三步,demo 仓库里有完整可跑的例子。 我建议先把示例原样跑起来,用 curl 分别请求带 callback 和不带 callback 的地址,确认两种响应都符合预期,再往自己的项目里搬。搬的时候别照抄全局注册,示例为了简单把它挂在了整个应用上,真实服务里只给需要兼容老页面的那几条路由用就够了。

curl "http://localhost:3000/?callback=fn"   # ;fn({"success":true,...})
curl "http://localhost:3000/"               # {"success":true,...}
@前端进阶之旅: 代码已经复制到剪贴板

demo地址

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/jsonp-use-middleware/

面试官追问

追问 1能不能把 demo 直接复制到业务项目里上线?
参考回答

不建议。demo 只证明了最小链路能跑,路由范围、回调名校验、错误处理和日志都没管。拿它当参考,接入时把这些补上。

追问 2示例跑起来了,但请求后没看到回调包装,先查什么?
参考回答

先确认请求参数名是不是 callback,中间件默认只认这个名字;再看 jsonp() 是否注册在设置 ctx.body 的中间件之前。顺序反了它就改写不了响应。

追问 3公司内网访问不了 GitHub,新人要复现这个例子怎么办?
参考回答

把示例代码连同 package.json 和锁文件放进内部仓库,固定版本号。示例总共就几十行,没必要依赖外网链接长期可用。

追问 4不带 callback 的请求,应该返回 JSON 还是 400?
参考回答

看接口定位。同时服务新老调用方的就返回普通 JSON;专门给老页面开的 .jsonp 路径,我倾向返回 400,让调用方早点发现参数没传。约定好写进接口文档,别让行为靠猜。

# 安装

30 秒速记

  • npm install --save koa-jsonp;npm 5 以后 --save 是默认行为,可以省略
  • 装完检查两处:package.json 的 dependencies 有它、锁文件也更新了,都要提交
  • 在实际 require('koa-jsonp') 的那个项目目录里装,monorepo 别装错位置
  • 本项目用 yarn 的话就 yarn add koa-jsonp,别混用包管理器,两份锁文件会打架
  • 别用全局安装,服务运行时依赖必须写进项目依赖

安装就一条命令 npm install --save koa-jsonp,npm 5 之后不加 --save 也会写进 dependencies。 真正要注意的是装在哪、记没记下来。要在实际引用它的那个项目目录里执行,装完确认 package.json 和锁文件都变了,一起提交,这样别人 npm ci 或 CI 构建时才能装上同一个版本。项目要是用 yarn 或 pnpm,就用对应的命令,混着用会出现两份锁文件,依赖版本对不上。

npm install --save koa-jsonp
@前端进阶之旅: 代码已经复制到剪贴板

理解「安装」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

koa-jsonp中间件|Nodejs专题系列

# koa-jsonp中间件

30 秒速记

  • koa-jsonp:业务照常写 ctx.body = 对象,中间件在返回阶段检测到 callback 参数就自动包成 cb(...)
  • 没带 callback 的请求原样返回 JSON,同一接口能同时服务新老调用方
  • 洋葱模型:jsonp() 要注册在设置 ctx.body 的中间件之前,它在 await next() 之后改写响应
  • 只给明确需要的路由挂,别全局包装;回调名校验、nosniff 要自己确认有没有做
  • 这个包很久没更新,用之前看它和当前 Koa 版本是否兼容;新项目直接 CORS

koa-jsonp 把手写 JSONP 那几行封装成了中间件,业务代码只管 ctx.body = data,要不要包成回调由它根据请求自动判断。 它利用的是 Koa 的洋葱模型:先 await next() 让下游把 ctx.body 设好,回来的时候看请求里有没有 callback,有就把响应改写成 cb(JSON) 并改掉响应类型,没有就原样放行。所以它必须注册在业务中间件前面。我用这种包会注意两点:只挂在需要兼容老页面的路由上;看一下它对回调名有没有做过滤,没有就自己在前面补一层校验。

理解「koa-jsonp中间件」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

koa-jsonp 解决的问题是:已经有一批返回 JSON 的接口,现在要让老页面也能用 JSONP 调,但不想每个接口都手写一遍拼接逻辑。

它的工作方式可以用一段等价代码理解:

function jsonp({ callbackName = 'callback' } = {}) {
  const CB_RE = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/
  return async (ctx, next) => {
    await next()                                   // 先让业务把 ctx.body 设好
    const cb = ctx.query[callbackName]
    if (!cb) return                                // 没有回调参数,原样返回 JSON
    if (!CB_RE.test(cb)) {
      ctx.status = 400
      ctx.body = 'invalid callback'
      return
    }
    ctx.type = 'text/javascript'
    ctx.set('X-Content-Type-Options', 'nosniff')
    ctx.body = `/**/ ;${cb}(${JSON.stringify(ctx.body)})`
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

关键在 await next() 这一行。Koa 的中间件像洋葱一样一层层进去再一层层出来,这个中间件在「出来」的时候才去看 ctx.body,所以它必须注册在设置响应的业务中间件之前:

app.use(jsonp())        // 外层:返回时包装
app.use(router.routes()) // 内层:设置 ctx.body
@前端进阶之旅: 代码已经复制到剪贴板

请求带了 ?callback=fn 就返回 fn({...}),没带就是普通 JSON。这种「按参数切换」的设计让同一个接口能同时服务新老调用方,代价是缓存要区分 callback 参数。实际项目里建议只把它挂在需要兼容的那几条路由上,而不是全局启用,避免所有接口都多出一个可执行脚本的出口。

面试官追问

追问 1有人说装了 koa-jsonp,POST 接口也能跨域调了,对吗?
参考回答

不对。JSONP 靠 <script> 发请求,只能 GET,也带不了自定义头。中间件只负责包装响应,管不了浏览器怎么发请求,POST 跨域要配 CORS。

追问 2app.use(jsonp()) 写在路由后面,为什么不生效?
参考回答

洋葱模型里,注册在后面的中间件只有前面调了 next() 才会进去,而且它包装响应依赖 await next() 之后的阶段。放在路由后面时,路由已经设好 body 返回了,它根本没机会改写。

追问 3同一个接口既给移动端返回 JSON,又给老页面返回 JSONP,缓存会不会出问题?
参考回答

会。CDN 要是只按路径缓存,先被老页面请求,移动端就可能拿到一段 cb(...) 脚本。缓存键要带上 callback 参数,或者干脆拆成两个路径。

追问 4线上报 callback is not defined,服务端看是 200,从哪查起?
参考回答

先抓响应体,看回调名是不是请求里传的那个;再看页面在插 script 前有没有定义这个函数。中间件默认的参数名是 callback,前端要是传的是 cb=xxx,就不会被识别成 JSONP。

追问 5这个包要不要用在新项目里?
参考回答

不用。新项目没有老客户端包袱,直接 @koa/cors 配白名单。koa-jsonp 这种老包维护不活跃,引进来以后升级 Koa 大版本时还得多查一次兼容性。

# koa-jsonp中间件

koa.js 官方wiki中也介绍了不少jsonp的中间件

其中koa-jsonp是支持koa2的,使用方式也非常简单,koa-jsonp的官方demo也很容易理解

# 快速使用

30 秒速记

  • 三步:npm install koa-jsonp → app.use(jsonp()) → 业务里照常 ctx.body = data
  • 请求带 ?callback=fn 返回 fn({...}),不带就是普通 JSON
  • 先跑通仓库里的 jsonp-use-middleware 示例,再接到自己的服务里
  • 接入时补上:只挂到需要的路由、回调名校验、callback 缺失时的约定(返回 JSON 还是 400)

快速上手就是安装、注册中间件、正常写业务这三步,demo 仓库里有完整可跑的例子。 我建议先把示例原样跑起来,用 curl 分别请求带 callback 和不带 callback 的地址,确认两种响应都符合预期,再往自己的项目里搬。搬的时候别照抄全局注册,示例为了简单把它挂在了整个应用上,真实服务里只给需要兼容老页面的那几条路由用就够了。

curl "http://localhost:3000/?callback=fn"   # ;fn({"success":true,...})
curl "http://localhost:3000/"               # {"success":true,...}
@前端进阶之旅: 代码已经复制到剪贴板

demo地址

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/jsonp-use-middleware/

面试官追问

追问 1能不能把 demo 直接复制到业务项目里上线?
参考回答

不建议。demo 只证明了最小链路能跑,路由范围、回调名校验、错误处理和日志都没管。拿它当参考,接入时把这些补上。

追问 2示例跑起来了,但请求后没看到回调包装,先查什么?
参考回答

先确认请求参数名是不是 callback,中间件默认只认这个名字;再看 jsonp() 是否注册在设置 ctx.body 的中间件之前。顺序反了它就改写不了响应。

追问 3公司内网访问不了 GitHub,新人要复现这个例子怎么办?
参考回答

把示例代码连同 package.json 和锁文件放进内部仓库,固定版本号。示例总共就几十行,没必要依赖外网链接长期可用。

追问 4不带 callback 的请求,应该返回 JSON 还是 400?
参考回答

看接口定位。同时服务新老调用方的就返回普通 JSON;专门给老页面开的 .jsonp 路径,我倾向返回 400,让调用方早点发现参数没传。约定好写进接口文档,别让行为靠猜。

# 安装

30 秒速记

  • npm install --save koa-jsonp;npm 5 以后 --save 是默认行为,可以省略
  • 装完检查两处:package.json 的 dependencies 有它、锁文件也更新了,都要提交
  • 在实际 require('koa-jsonp') 的那个项目目录里装,monorepo 别装错位置
  • 本项目用 yarn 的话就 yarn add koa-jsonp,别混用包管理器,两份锁文件会打架
  • 别用全局安装,服务运行时依赖必须写进项目依赖

安装就一条命令 npm install --save koa-jsonp,npm 5 之后不加 --save 也会写进 dependencies。 真正要注意的是装在哪、记没记下来。要在实际引用它的那个项目目录里执行,装完确认 package.json 和锁文件都变了,一起提交,这样别人 npm ci 或 CI 构建时才能装上同一个版本。项目要是用 yarn 或 pnpm,就用对应的命令,混着用会出现两份锁文件,依赖版本对不上。

npm install --save koa-jsonp
@前端进阶之旅: 代码已经复制到剪贴板

理解「安装」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1npm install 没报错,启动时却提示 Cannot find module 'koa-jsonp',为什么?
参考回答

多半是装错了目录,比如在仓库根目录装了,服务却在子目录里跑;或者装完没提交 package.json,CI 上压根没装。看一下 require.resolve('koa-jsonp') 能不能解析到就知道。

追问 2monorepo 里只有一个子项目用它,装在根目录行不行?
参考回答

别这么干。依赖要声明在真正用它的子项目里,装在根目录本地能靠提升碰巧找到,子项目单独部署时就缺了。用 workspace 的话按它的规则 yarn workspace api add koa-jsonp。

追问 3--save 和 --save-dev 该选哪个?
参考回答

koa-jsonp 是服务运行时要用的,放 dependencies。--save-dev 是给测试、构建工具这类只在开发阶段用的包,生产环境 npm ci --omit=dev 时会被跳过,放错了线上就找不到。

追问 4有人图省事 npm i -g koa-jsonp 全局装,有什么问题?
参考回答

require 默认不会去全局目录找,项目里照样找不到;就算能找到,换一台机器就没了。服务依赖必须写进项目的 package.json。

# 简单例子

← 9.1 原生koa2实现JSONP操作数据库 →

fe